﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Diagnostics;

namespace IcqSharp
{
    public class BuddyFamilyHandler //: FamilyHandler
    {

        public static void Handle(MemoryStream stream, SNACHeader header)
        {
            switch (header.Subtype)
            {
                case (UInt16)BuddyFamilySubtype.Online:
                    HandleUserOnline(stream, header); break;

                case (UInt16)BuddyFamilySubtype.Offline:
                    HandleUserOffline(stream, header); break;

                case (UInt16)BuddyFamilySubtype.SrvReplyBuddy:
                    HandleReplyBuddy(stream, header); break;

                case (UInt16)BuddyFamilySubtype.NotifyRejected:
                    HandleNotifyRejected(stream, header); break;

                case (UInt16)BuddyFamilySubtype.Error:
                    HandleBuddyError(stream, header); break;

                default:
                    // Warning: Ignoring SNAC(x%02x,x%02x) - 
                    // Unknown SNAC (Flags: %u, Ref: %u)", 
                    // ICQ_BUDDY_FAMILY, 
                    // pSnacHeader->wSubtype, 
                    // pSnacHeader->wFlags, 
                    // pSnacHeader->dwRef);
                    break;
            }
        }

        public static Boolean IsUIN(String s)
        {
            return s.Count(c => !Char.IsDigit(c)) == 0;
        }

        public static Boolean ReadUID(MemoryStream stream, out UInt32 UIN, out String UID)
        {
            // Sender UIN
            var uid_len = (Byte)stream.ReadByte();

            if (uid_len > 11)
            {
                // Malformed UIN in packet
                Debugger.Break();
                UIN = 0; UID = null;
                return false;
            }

            var s = stream.ReadAsciiString(uid_len);

            if (IsUIN(s))
            {
                UIN = UInt32.Parse(s);
                UID = null;
            }
            else
            {
                UIN = 0; UID = s;
            }

            return true;
        }

        public static Boolean UnpackSessionDataItem(TLVChain Chain, UInt16 ItemType, out Byte[] ItemData, out UInt16 ItemSize, out Byte ItemFlags)
        {
            ItemData = null;
            ItemSize = 0;
            ItemFlags = 0;

            var tlv = Chain.Find(0x1D, 1);
            if (tlv == null) return false;

            var buf = new MemoryStream(tlv.Data);
            while (buf.Length - buf.Position >= 4)
            {
                // parse session data items one by one
                var itemType = buf.ReadUInt16();
                var itemFlags = (Byte)buf.ReadByte();
                var itemLen = (Byte)buf.ReadByte();

                // just some validity check
                if (itemLen > buf.Length - buf.Position)
                    itemLen = (Byte)(buf.Length - buf.Position);

                // found
                if (itemType == ItemType)
                {
                    ItemData = tlv.Data;
                    ItemSize = itemLen;
                    ItemFlags = itemFlags;
                    return true;
                }
            }

            return false;
        }


        public static void HandleUserOnline(MemoryStream stream, SNACHeader header)
        {
            UInt32 Port = 0;
            UInt32 RealIP = 0;
            UInt32 UIN;
            String UID;
            UInt32 DirectConnCookie = 0;
            UInt32 WebPort = 0;
            UInt32 FT1 = 0, FT2 = 0, FT3 = 0;
            String Client = null;
            Byte ClientId = 0;
            UInt16 Version = 0;
            UInt16 TLVCount;
            UInt16 WarningLevel;
            UInt16 StatusFlags;
            UInt16 Status = 0, OldStatus = 0;
            Byte TCPFlag = 0;
            String StrBuf;
            List<Guid> capabilities = new List<Guid>();
            Contact contact;

            // Unpack the sender's user ID
            if (!ReadUID(stream, out UIN, out UID)) return;

            // Syntax check
            if (Session.client.Available < 4)
                return;

            // Warning level?
            WarningLevel = stream.ReadUInt16();

            // TLV count
            TLVCount = stream.ReadUInt16();

            // Determine contact
            contact = Contacts.Find(UIN, UID);

            // Ignore status notification if the user is not already on our list
            if (contact == null)
            {
                // Ignoring user online (%s), strUID(dwUIN, szUID));
                // return;
            }

            // Read user info TLVs
            TLVChain chain;
            TLV tlv;

            // Syntax check
            if (stream.Available() < 4)
                return;

            // Get chain
            chain = new TLVChain(stream, TLVCount);
            if (chain == null)
                return;

            // Get Class word
            var _class = chain.GetWord(0x01, 1);
            var IsICQ = (_class & (UInt32)Class.ICQ) > 0;

            if (UIN != 0)
            {
                // Get DC info TLV
                tlv = chain.Find(0x0C, 1);
                if (tlv != null && (tlv.Len >= 15))
                {
                    var buffer = new MemoryStream(tlv.Data);

                    IsICQ = true;

                    RealIP = buffer.ReadUInt32();
                    Port = buffer.ReadUInt32();
                    TCPFlag = (Byte)buffer.ReadByte();
                    Version = buffer.ReadUInt16();
                    DirectConnCookie = buffer.ReadUInt32();
                    WebPort = buffer.ReadUInt32(); // Web front port

                    buffer.Seek(+4, SeekOrigin.Current); // Client features

                    // Get faked time signatures, used to identify clients
                    if (tlv.Len >= 0x23)
                    {
                        FT1 = buffer.ReadUInt32();
                        FT2 = buffer.ReadUInt32();
                        FT3 = buffer.ReadUInt32();
                    }
                }
                else
                {
                    // This client doesnt want DCs
                }

                // Get Status info TLV
                tlv = chain.Find(0x06, 1);
                if (tlv != null && (tlv.Len >= 4))
                {
                    var buffer = new MemoryStream(tlv.Data);

                    StatusFlags = buffer.ReadUInt16();
                    Status = buffer.ReadUInt16();
                }
                else if (!IsICQ)
                {
                    // Connected thru AIM client, guess by user class
                    if ((_class & (UInt16)Class.Away) > 0)
                        Status = (UInt16)IcqStatus.Away;
                    else if ((_class & (UInt16)Class.Wireless) > 0)
                        Status = (UInt16)IcqStatus.OnThePhone;
                    else
                        Status = (UInt16)IcqStatus.Online;

                    StatusFlags = 0;
                }
                else
                {
                    // Huh? No status TLV? Lets guess then...
                    StatusFlags = 0;
                    Status = (UInt16)IcqStatus.Online;
                }
            }
            else
            {
                IsICQ = false;

                if ((_class & (UInt16)Class.Away) > 0)
                    Status = (UInt16)IcqStatus.Away;
                else if ((_class & (UInt16)Class.Wireless) > 0)
                    Status = (UInt16)IcqStatus.OnThePhone;
                else
                    Status = (UInt16)IcqStatus.Online;

                StatusFlags = 0;
            }

            // Flags are %x, wStatusFlags
            // Status is %x, wStatus

            // Get IP TLV
            var IP = chain.GetDWord(0x0A, 1);

            // Get Online Since TLV
            var OnlineSince = chain.GetDWord(0x03, 1);

            // Get Away Since TLV
            var AwaySince = chain.GetDWord(0x29, 1);

            // Get Member Since TLV
            var MemberSince = chain.GetDWord(0x05, 1);

            // Get Idle timer TLV
            var IdleTimer = chain.GetWord(0x04, 1);
            var Idle = DateTime.Now.AddMinutes(-IdleTimer);


            // Check client capabilities
            if (contact != null)
            {
                OldStatus = contact.Status;

                // Collect all Capability info from TLV chain
                Byte[] capBuf = null;
                UInt16 capLen = 0;

                // Get Location Capability Info TLVs
                var FullTLV = chain.Find(0x0D, 1);
                var ShortTLV = chain.Find(0x19, 1);

                if (FullTLV != null && (FullTLV.Len >= 16))
                    capLen += FullTLV.Len;

                if (ShortTLV != null && (ShortTLV.Len >= 2))
                    capLen += (UInt16)(ShortTLV.Len * 8);


                // Capabilities

                // classic
                var caps_data = new MemoryStream(FullTLV.Data);
                while (caps_data.Length - caps_data.Position >= 16)
                {
                    var cap = new Guid(caps_data.ReadBytes(16));
                    if (!capabilities.Contains(cap))
                        capabilities.Add(cap);
                }

                // short
                caps_data = new MemoryStream(ShortTLV.Data);
                while (caps_data.Length - caps_data.Position >= 2)
                {
                    var value_bytes = caps_data.ReadBytes(2);
                    var cap_bytes = new Byte[16];
                    cap_bytes[2] = value_bytes[0];
                    cap_bytes[3] = value_bytes[1];
                    var cap = new Guid(cap_bytes);
                    if (!capabilities.Contains(cap))
                        capabilities.Add(cap);
                }


                if (capabilities.Count > 0)
                {
                    // Update the contact's capabilies if present in packet
                    contact.Capabilities = Capabilities.GetCapabilities(capabilities);
                    // SetCapabilitiesFromBuffer(hContact, capBuf, capLen, wOldStatus == ID_STATUS_OFFLINE);

                    // detect client
                    //szClient = detectUserClient(hContact, nIsICQ, wClass, dwOnlineSince, szCurrentClient, wVersion, dwFT1, dwFT2, dwFT3, nTCPFlag, dwDirectConnCookie, dwWebPort, capBuf, capLen, &bClientId, szStrBuf);
                }
                else if (OldStatus == (UInt16)IcqStatus.Offline)
                {
                    // Remove the contact's capabilities if coming from offline
                    //ClearAllContactCapabilities(hContact);

                    // no capability
                    //NetLog_Server("No capability info TLVs");

                    // detect client
                    //szClient = detectUserClient(hContact, nIsICQ, wClass, dwOnlineSince, NULL, wVersion, dwFT1, dwFT2, dwFT3, nTCPFlag, dwDirectConnCookie, dwWebPort, NULL, capLen, &bClientId, szStrBuf);
                }
                else
                {
                    // Capabilities not present in update packet, do not touch
                    //szClient = (const char*)-1; // we don't want to client be overwritten
                }

                // handle Xtraz status
                //char *moodData = NULL;
                UInt16 moodSize = 0;

                Byte[] moodData;
                Byte moodFlags;

                UnpackSessionDataItem(chain, (UInt16)0xE, out moodData, out moodSize, out moodFlags);



                if (capabilities.Count > 0 || OldStatus == (UInt16)IcqStatus.Offline)
                    ;
                //handleXStatusCaps(dwUIN, szUID, hContact, capBuf, capLen, moodData, moodSize);
                else
                    ;
                //handleXStatusCaps(dwUIN, szUID, hContact, NULL, 0, moodData, moodSize);

                // Determine support for extended status messages
                if (chain.GetWord(0x08, 1) == 0x0A06)
                    ; //SetContactCapabilities(hContact, CAPF_STATUS_MESSAGES);
                else if (OldStatus == (UInt16)IcqStatus.Offline)
                    ; // ClearContactCapabilities(hContact, CAPF_STATUS_MESSAGES);


                if (OldStatus == (UInt16)IcqStatus.Offline)
                {
                    //if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY))
                    //	NetLog_Server("Supports advanced messages");
                    //else
                    //	NetLog_Server("Does NOT support advanced messages");
                    ;
                }


                if (!IsICQ)
                {
                    // AIM clients does not advertise these, but do support them
                    ; //SetContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING);
                    // Server relayed messages are only supported by ICQ clients
                    ; //ClearContactCapabilities(hContact, CAPF_SRV_RELAY);

                    //if (dwUIN && wOldStatus == ID_STATUS_OFFLINE)
                    //	NetLog_Server("Logged in with AIM client");
                }

                if (IsICQ && Version < 8)
                {
                    ; // ClearContactCapabilities(hContact, CAPF_SRV_RELAY);
                    //if (wOldStatus == ID_STATUS_OFFLINE)
                    //	NetLog_Server("Forcing simple messages due to compability issues");
                }

                // Process Avatar Hash
                tlv = chain.Find(0x1D, 1);
                if (tlv != null)
                    ; //handleAvatarContactHash(dwUIN, szUID, hContact, pTLV->pData, pTLV->wLen, wOldStatus);
                else
                    ; //handleAvatarContactHash(dwUIN, szUID, hContact, NULL, 0, wOldStatus);

                // Process Status Note
                ; //parseStatusNote(dwUIN, szUID, hContact, pChain);
            }
            // Free TLV chain
            //disposeChain(&pChain);

            // Save contacts details in database
            if (contact != null)
            {
                contact.OnlineSince = new DateTime(OnlineSince);
                contact.AwaySince = new DateTime(AwaySince);
                contact.Idle = Idle;

                if (MemberSince != 0)
                    contact.MemberSince = new DateTime(MemberSince);

                if (IsICQ)
                {
                    // on AIM these are not used
                    contact.DirectCookie = DirectConnCookie;
                    contact.DCType = TCPFlag;
                    contact.UserPort = (UInt16)(Port & 0xFFFF);
                    contact.Version = Version;
                }
                else
                {
                    //deleteSetting(hContact,   "DirectCookie");
                    //deleteSetting(hContact,   "DCType");
                    //deleteSetting(hContact,   "UserPort");
                    //deleteSetting(hContact,   "Version");
                }

                //if (!szClient)
                {
                    // if no detection, set uknown
                    //szClient = (nIsICQ ? "Unknown" : "Unknown AIM");		
                }
                //if (szClient != (char*)-1)
                {
                    //setSettingStringUtf(hContact, "MirVer",   szClient);
                    //setSettingByte(hContact,  "ClientID",     bClientId);
                }

                if (OldStatus == (UInt16)IcqStatus.Offline)
                {
                    contact.IP = new IPAddress(IP);
                    contact.RealIP = new IPAddress(RealIP);
                }
                else
                {
                    // if not first notification only write significant information
                    if (IP != 0)
                        contact.IP = new IPAddress(IP);
                    if (RealIP != 0)
                        contact.RealIP = new IPAddress(RealIP);
                }

                contact.Status = Status;

                // Update info?
                if (UIN != 0)
                {
                    // check if the local copy of user details is up-to-date
                    ; // if (IsMetaInfoChanged(hContact))
                    //  icq_QueueUser(hContact);
                }
            }

            //if (wOldStatus != IcqStatusToMiranda(wStatus))
            //{ 
            //    // And a small log notice... if status was changed
            //    if (nIsICQ)
            //        NetLog_Server("%u changed status to %s (v%d).", dwUIN, MirandaStatusToString(IcqStatusToMiranda(wStatus)), wVersion);
            //    else
            //        NetLog_Server("%s changed status to %s.", strUID(dwUIN, szUID), MirandaStatusToString(IcqStatusToMiranda(wStatus)));
            //}
            //else
            //{
            //    if (nIsICQ)
            //        NetLog_Server("%u has status %s (v%d).", dwUIN, MirandaStatusToString(IcqStatusToMiranda(wStatus)), wVersion);
            //    else
            //        NetLog_Server("%s has status %s.", strUID(dwUIN, szUID), MirandaStatusToString(IcqStatusToMiranda(wStatus)));
            //}


            //if (szClient == cliSpamBot)
            //{
            //    if (getSettingByte(NULL, "KillSpambots", DEFAULT_KILLSPAM_ENABLED) && DBGetContactSettingByte(hContact, "CList", "NotOnList", 0))
            //    { // kill spammer
            //        icq_DequeueUser(dwUIN);
            //        icq_sendRemoveContact(dwUIN, NULL);
            //        AddToSpammerList(dwUIN);
            //        if (getSettingByte(NULL, "PopupsSpamEnabled", DEFAULT_SPAM_POPUPS_ENABLED))
            //            ShowPopUpMsg(hContact, LPGEN("Spambot Detected"), LPGEN("Contact deleted & further events blocked."), POPTYPE_SPAM);
            //        CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);

            //        NetLog_Server("Contact %u deleted", dwUIN);
            //    }
            //}

        }
        public static void HandleUserOffline(MemoryStream stream, SNACHeader header)
        {
            //    DWORD dwUIN;
            //    uid_str szUID;

            //    do {
            //        oscar_tlv_chain *pChain = NULL;
            //        WORD wTLVCount;
            //        DWORD dwAwaySince;

            //        // Unpack the sender's user ID
            //        if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return;

            //        // Warning level?
            //        buf += 2;

            //        // TLV Count
            //        unpackWord(&buf, &wTLVCount);
            //        wLen -= 4;

            //        // Skip the TLV chain
            //        while (wTLVCount && wLen >= 4)
            //        {
            //            WORD wTLVType;
            //            WORD wTLVLen;

            //            unpackWord(&buf, &wTLVType);
            //            unpackWord(&buf, &wTLVLen);
            //            wLen -= 4;

            //            // stop parsing overflowed packet
            //            if (wTLVLen > wLen) 
            //            {
            //                disposeChain(&pChain);
            //                return;
            //            }

            //            if (wTLVType == 0x1D)
            //            { // read only TLV with Session data into chain
            //                BYTE *pTLV = buf - 4;
            //                disposeChain(&pChain);
            //                pChain = readIntoTLVChain(&pTLV, wLen + 4, 1);
            //            }
            //            else if (wTLVType == 0x29 && wTLVLen == sizeof(DWORD))
            //            { // get Away Since value
            //                BYTE *pData = buf;
            //                unpackDWord(&pData, &dwAwaySince);
            //            }

            //            buf += wTLVLen;
            //            wLen -= wTLVLen;
            //            wTLVCount--;
            //        }

            //        // Determine contact
            //        HANDLE hContact = HContactFromUID(dwUIN, szUID, NULL);

            //        // Skip contacts that are not already on our list or are already offline
            //        if (hContact != INVALID_HANDLE_VALUE)
            //        {
            //            WORD wOldStatus = getContactStatus(hContact);

            //            // Process Avatar Hash
            //            oscar_tlv *pAvatarTLV = pChain ? pChain->getTLV(0x1D, 1) : NULL;
            //            if (pAvatarTLV)
            //                handleAvatarContactHash(dwUIN, szUID, hContact, pAvatarTLV->pData, pAvatarTLV->wLen, wOldStatus);
            //            else
            //                handleAvatarContactHash(dwUIN, szUID, hContact, NULL, 0, wOldStatus);

            //            // Process Status Note (offline status note)
            //            parseStatusNote(dwUIN, szUID, hContact, pChain);

            //            // Update status times
            //            setSettingDword(hContact, "IdleTS", 0);
            //            setSettingDword(hContact, "AwayTS", dwAwaySince);

            //            // Clear custom status & mood
            //            char tmp = NULL;
            //            handleXStatusCaps(dwUIN, szUID, hContact, (BYTE*)&tmp, 0, &tmp, 0);

            //            if (wOldStatus != ID_STATUS_OFFLINE)
            //            {
            //                NetLog_Server("%s went offline.", strUID(dwUIN, szUID));

            //                setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
            //                // close Direct Connections to that user
            //                CloseContactDirectConns(hContact);
            //                // Reset DC status
            //                setSettingByte(hContact, "DCStatus", 0);
            //            }
            //            else
            //                NetLog_Server("%s is offline.", strUID(dwUIN, szUID));
            //        }

            //        // Release memory
            //        disposeChain(&pChain);
            //    }
            //    while (wLen >= 1);
        }
        public static void HandleReplyBuddy(MemoryStream stream, SNACHeader header)
        {

        }
        public static void HandleNotifyRejected(MemoryStream stream, SNACHeader header)
        {

        }
        public static void HandleBuddyError(MemoryStream stream, SNACHeader header)
        {
            var Error = (stream.Available() >= 2) ?
                stream.ReadUInt16() : 0;

            //LogFamilyError(ICQ_BUDDY_FAMILY, wError);
            Debugger.Break();
        }

    }
}
